;;; mpvi-websocket.el --- Communicate through websocket -*- lexical-binding: t -*-

;; Copyright (C) 2023 lorniu <lorniu@gmail.com>

;; Author: lorniu <lorniu@gmail.com>
;; URL: https://github.com/lorniu/mpvi
;; SPDX-License-Identifier: MIT

;;; Commentary:

;; Communicate through websocket from external program such as browsers.
;;
;; This allows play videos through mpvi in Chrome, Edge and Firefox.
;;
;; Invoke `mpvi-generate-browser-extension' to generate the files and install
;; them according to the guide, then you will find `play with mpvi' in the
;; context menu of links.
;;
;; Make sure the websocket server is actived with `mpvi-websocket' for this.

;;; Code:

(require 'json)

(defvar mpvi-websocket-port 19123
  "The port for the WebSocket server that the extension will connect to.")

(defvar mpvi-websocket-server nil)

(declare-function mpvi-start "mpvi" t)
(declare-function mpvi-log "mpvi" t)
(declare-function websocket-server "websocket" t)
(declare-function websocket-server-close "websocket" t)
(declare-function websocket-frame-text "websocket" t)

;;;###autoload
(defun mpvi-websocket ()
  "Start a WebSocket server to listen for browser extensions.
If a WebSocket server is already running, just stop it."
  (interactive)
  (unless (require 'websocket nil t)
    (user-error "You should install package `websocket' first"))
  (if mpvi-websocket-server
      (ignore-errors
        (websocket-server-close mpvi-websocket-server)
        (setq mpvi-websocket-server nil)
        (message "mpvi WebSocket server stopped."))
    (setq mpvi-websocket-server
          (websocket-server
           mpvi-websocket-port
           :host "127.0.0.1"
           :on-message (lambda (_ws frame)
                         (when-let* ((msg (websocket-frame-text frame))
                                     (url msg))
                           (mpvi-log "[mpvi-websocket] received URL: %s" url)
                           (condition-case err
                               (mpvi-start url 'browser)
                             (error (message "%s" err)))))
           :on-open (lambda (_) (mpvi-log "[mpvi-websocket] client connected"))
           :on-close (lambda (_) (mpvi-log "[mpvi-websocket] client disconnected"))))
    (message "mpvi WebSocket server started on ws://127.0.0.1:%s" mpvi-websocket-port)))

;;;###autoload
(defun mpvi-install-browser-extension ()
  "Generate a browser extension for sending links to mpvi via WebSocket."
  (interactive)

  (let ((target-dir (expand-file-name "mpvi-extension" (read-directory-name "Extenson save to: " nil nil t))))
    (unless (file-directory-p target-dir)
      (make-directory target-dir t))

    ;; 1. Generate manifest.json
    (let* ((manifest-v3 `(("manifest_version" . 3)
                          ("name" . "Send to mpvi in Emacs")
                          ("version" . "1.0.0")
                          ("description" . "Send video links to mpvi in Emacs.")
                          ("permissions" . ("contextMenus","notifications"))
                          ("host_permissions" . ("ws://localhost/*"))
                          ("background" . (("service_worker" . "background.js")))))
           (manifest-content (json-encode manifest-v3)))
      (with-temp-file (expand-file-name "manifest.json" target-dir)
        (insert manifest-content)))

    ;; 2. Generate background.js
    (let ((background-script
           (format "const PORT = %d;

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: 'send-to-mpvi-with-websocket',
    title: 'Play with mpvi',
    contexts: ['link', 'video', 'page'],
  });
});

function showNotification(title, message) {
  chrome.notifications.create('mpvi-notification-' + Math.trunc(Math.random() * 1000), {
    type: 'basic',
    iconUrl: '',
    title: title,
    message: message,
    priority: 1
  });
}

function sendToMpvi(url) {
  const socket = new WebSocket(`ws://localhost:${PORT}`);
  socket.onopen = () => {
    socket.send(url);
    socket.close();
  };
  socket.onerror = () => {
    showNotification('play with mpvi', 'Could not connect to mpvi in Emacs. Ensure the WebSocket server is running with `mpvi-websocket` in Emacs.');
  };
  socket.onclose = () => {};
}

chrome.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === 'send-to-mpvi-with-websocket') {
    const urlToSend = info.linkUrl || info.srcUrl || info.pageUrl;
    if (urlToSend) { sendToMpvi(urlToSend); }
  }
});
" mpvi-websocket-port)))
      (with-temp-file (expand-file-name "background.js" target-dir)
        (insert background-script)))

    ;; 3. Provide instructions to the user
    (message "Browser extension generated in: %s" (abbreviate-file-name target-dir))
    (with-current-buffer (get-buffer-create "*MPVI Extension Instructions*")
      (erase-buffer)
      (insert (format "Browser Extension Generated!\n\nLocation: %s\n\n" target-dir))
      (insert "To install the extension:\n\n")
      (insert "1. Open your browser's extension management page:\n")
      (insert "   - Chrome/Edge: chrome://extensions\n")
      (insert "   - Firefox: about:addons\n\n")
      (insert "2. Enable \"Developer mode\" (usually a toggle in the side corner).\n\n")
      (insert "3. Click \"Load unpacked\" and select the following directory:\n")
      (insert (format "   %s\n\n" (file-name-as-directory target-dir)))
      (insert "4. The \"Play with mpvi\" option should now appear in your right-click menu.\n\n")
      (insert "Remember to run `M-x mpvi-websocket` in Emacs.\n")
      (display-buffer (current-buffer)))))

(provide 'mpvi-websocket)

;;; mpvi-websocket.el ends here
